package cn.jiangjiesheng.bootstrap.filestore.impl;

import cn.jiangjiesheng.bootstrap.filestore.exception.InvalidLocalFileException;
import cn.jiangjiesheng.bootstrap.filestore.properties.FileStoreProperties;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import cn.jiangjiesheng.bootstrap.filestore.dto.ResultMap;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 阿里云OSS
 * <p>
 * Created by dev@jiangjiesheng.cn on 2021/7/9  <br/>
 */
@Slf4j
public class AliyunOssFile extends AbstractFileStore<OSS> {

    public AliyunOssFile(FileStoreProperties fileStoreProperties) {
        log.debug("Initializing aliyun oss client");
        this.setFileStoreProperties(fileStoreProperties);
        this.initializeClient(fileStoreProperties);
    }

    @Override
    public OSS initializeClient(FileStoreProperties properties) {
        if (Objects.isNull(properties.getSecurityToken())) {
            setStoreClient(new OSSClientBuilder().build(properties.getEndpoint(),
                    properties.getAk(),
                    properties.getSk()));
        } else {
            // 需要生成临时访问路由时，必须
            setStoreClient(new OSSClientBuilder().build(properties.getEndpoint(),
                    properties.getAk(),
                    properties.getSk(),
                    properties.getSecurityToken()
            ));
        }
        return getStoreClient();
    }

    /**
     * 大文件分段上传
     *
     * @param bucket
     * @param objectId
     * @param filepath
     * @return
     */
    @Override
    public String putBigObject(String bucket, String objectId, String filepath) {
        final OSS ossClient = getStoreClient(OSS.class);
        // 初始化线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        final File largeFile = new File(filepath);
        FileInputStream fileInputStream;
        try {
            fileInputStream = new FileInputStream(largeFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new InvalidLocalFileException();
        }
        // 初始化分段上传任务
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucket, objectId);
        InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
        final String uploadId = result.getUploadId();
        log.info("UploadPart {}", uploadId + "\n");

        // 每段上传10MB
        long partSize = 10 * 1024 * 1024L;
        long fileSize = largeFile.length();
        // 计算需要上传的段数
        long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;
        final List<PartETag> partEtags = Collections.synchronizedList(new ArrayList<PartETag>());
        // 执行并发上传段
        for (int i = 0; i < partCount; i++) {
            // 分段在文件中的起始位置
            final long offset = i * partSize;
            // 分段大小
            final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
            // 分段号
            final int partNumber = i + 1;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    byte[] bufferBytes = new byte[(int) currPartSize];
                    try {
                        int reads = fileInputStream.read(bufferBytes, (int) offset, (int) currPartSize);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
                    uploadPartRequest.setBucketName(bucket);
                    uploadPartRequest.setKey(objectId);
                    uploadPartRequest.setUploadId(uploadId);
                    uploadPartRequest.setInputStream(new ByteArrayInputStream(bufferBytes));
                    uploadPartRequest.setPartSize(currPartSize);
                    uploadPartRequest.setPartNumber(partNumber);
                    UploadPartResult uploadPartResult;
                    uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                    log.info("UploadPart Part# {} done", partNumber);
                    partEtags.add(uploadPartResult.getPartETag());
                }
            });
        }
        // 等待上传完成
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            try {
                executorService.awaitTermination(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("UploadPart {} {}", e.getMessage(), e);
            }
        }
        // 合并段
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucket, objectId, uploadId, partEtags);
        ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        return uploadId;
    }

    @Override
    public String putBigObject(String objectId, String filepath) {
        return this.putBigObject(getFileStoreProperties().getDefaultBucket(), objectId, filepath);
    }

    @Override
    public void putObject(String bucket, String objectId, InputStream fileData) {
        getStoreClient().putObject(bucket, objectId, fileData);
    }


    @Override
    public InputStream getObject(String bucket, String objectId) {
        return getStoreClient().getObject(bucket, objectId).getObjectContent();
    }

    @Override
    public void publicRead(String bucket, String objectId, boolean enabled) {
        if (enabled) {
            getStoreClient().setObjectAcl(bucket, objectId, CannedAccessControlList.PublicRead);
        } else {
            getStoreClient().setObjectAcl(bucket, objectId, CannedAccessControlList.Private);
        }
    }


    @Override
    public void copyObject(String bucket, String objectId, String targetBucket, String targetObjectId) {
        getStoreClient().copyObject(bucket, objectId, targetBucket, targetObjectId);
    }


    @Override
    public Boolean doesBucketExists(String bucket) {
        return getStoreClient().doesBucketExist(bucket);
    }


    @Override
    public void createBucket(String bucket) {
        getStoreClient().createBucket(bucket);
    }


    @Override
    public void deleteObject(String bucket, String objectId) {
        getStoreClient().deleteObject(bucket, objectId);
    }


    @Override
    public ResultMap deleteMultipleObjects(String bucket, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            getStoreClient().deleteObject(bucket, objectId);
            resultMap.put(objectId, true);
        }
        return resultMap;
    }


    @Override
    public ResultMap doesObjectsExists(String bucket, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            resultMap.put(objectId, doesObjectExists(bucket, objectId));
        }
        return resultMap;
    }


    @Override
    public Boolean doesObjectExists(String bucket, String objectId) {
        return getStoreClient().doesObjectExist(bucket, objectId);
    }


    @Override
    public ResultMap getObjectMeta(String bucket, String objectId) {
        ObjectMetadata objectMetadata = getStoreClient().getObjectMetadata(bucket, objectId);
        ResultMap resultMap = new ResultMap();
        resultMap.put("lastModified", objectMetadata.getLastModified());
        resultMap.put("size", objectMetadata.getContentLength());
        resultMap.put("contentType", objectMetadata.getContentType());
        return resultMap;
    }


    @Override
    public ResultMap generatePreSignedURL(String bucket, String... objectIds) {
        return generatePreSignedURL(bucket, new Date(new Date().getTime() + 3600 * 1000), objectIds);
    }


    @Override
    public ResultMap generatePreSignedURL(String bucket, Date expiration, String... objectIds) {
        ResultMap resultMap = new ResultMap();
        for (String objectId : objectIds) {
            String signedUrl = getStoreClient().generatePresignedUrl(bucket, objectId, expiration).toString();
            resultMap.put(objectId, signedUrl);
        }
        return resultMap;
    }

    @Override
    public void putObject(String objectId, InputStream fileData) {
        this.putObject(this.getFileStoreProperties().getDefaultBucket(), objectId, fileData);
    }

    @Override
    public InputStream getObject(String objectId) {
        return this.getObject(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public void deleteObject(String objectId) {
        this.deleteObject(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap deleteMultipleObjects(String... objectIds) {
        return this.deleteMultipleObjects(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public ResultMap doesObjectsExists(String... objectIds) {
        return this.doesObjectsExists(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public Boolean doesObjectExists(String objectId) {
        return this.doesObjectExists(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap getObjectMeta(String objectId) {
        return this.getObjectMeta(getFileStoreProperties().getDefaultBucket(), objectId);
    }

    @Override
    public ResultMap generatePreSignedURL(String objectId) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), new String[]{objectId});
    }

    @Override
    public ResultMap generatePreSignedURL(String... objectIds) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), objectIds);
    }

    @Override
    public ResultMap generatePreSignedURL(Date expiration, String... objectIds) {
        return this.generatePreSignedURL(getFileStoreProperties().getDefaultBucket(), expiration, objectIds);
    }


    @Override
    public void close() {
        getStoreClient().shutdown();
    }
}
